#include "CBL_Thread.h"
#include "CBL_Debug.h"
#include "CBL_Cpu.h"

CB_SUBNAMESPACE_START(Cpu)

#ifdef CB_WINDOWS

// ---------------------------------------------------------------------------------------------
// Windows Thread implementation
// ---------------------------------------------------------------------------------------------

CThread::CThread(const char* _pName, uint32 _Granularity)
: m_Granularity(_Granularity)
, m_Paused(false)
, m_Running(false)
, m_Thread(NULL)
, m_pName(_pName)
{
}

// ---------------------------------------------------------------------------------------------

CThread::~CThread()
{
	Stop();
	CloseHandle(m_Thread);
}

// ---------------------------------------------------------------------------------------------
	
void CThread::Run(void* _pData)
{
	CB_CRITICAL_SECTION_GUARD(m_RunMutex);

	if (Debug::Verify(!m_Running, "Thread already running"))
    {
		// Note: If closehandle is not threadsafe this is problematic when
		// starting a thread on this object while wating in stop()
		CloseHandle(m_Thread);
    	SThreadParameters* pParams = new SThreadParameters();
    
        pParams->m_Granularity = m_Granularity;
        pParams->m_pObject     = this;
        pParams->m_pData       = _pData;
        pParams->m_Once        = false;
        
		m_Thread = CreateThread(NULL, 0, &CThread::ThreadMain, (void*)pParams, 0, &m_ThreadId);
		m_Running = true;
    }
}

// ---------------------------------------------------------------------------------------------

void CThread::RunOnce(void* _pData)
{
	CB_CRITICAL_SECTION_GUARD(m_RunMutex);

    if (Debug::Verify(!m_Running, "Thread already running"))
    {
		// Note: If closehandle is not threadsafe this is problematic when
		// starting a thread on this object while wating in stop()
		CloseHandle(m_Thread);

    	SThreadParameters* pParams = new SThreadParameters();
    
        pParams->m_Granularity = 0;
        pParams->m_pObject     = this;
        pParams->m_pData       = _pData;
        pParams->m_Once        = true;
		
        m_Thread = CreateThread(NULL, 0, &CThread::ThreadMain, (void*)pParams, 0, &m_ThreadId);
		m_Running = true;
	}
}

// ---------------------------------------------------------------------------------------------

void CThread::Join()
{
	if (CriticalSectionTest(m_RunMutex, m_Running))
		WaitForSingleObject(m_Thread, INFINITE);
}

// ---------------------------------------------------------------------------------------------

void CThread::Pause()
{
	CB_CRITICAL_SECTION_GUARD(m_PauseMutex);
	m_Paused = true;
}

// ---------------------------------------------------------------------------------------------

void CThread::Resume()
{
	CB_CRITICAL_SECTION_GUARD(m_PauseMutex);
	m_Paused = false;
}

// ---------------------------------------------------------------------------------------------

bool CThread::IsPaused()
{
	return CriticalSectionTest(m_PauseMutex, m_Paused);
}
	
// ---------------------------------------------------------------------------------------------

void CThread::Stop()
{
	m_RunMutex.Lock();

	if (m_Running)
	{
		m_Running = false;
		Resume();

		m_RunMutex.Unlock();
		WaitForSingleObject(m_Thread, INFINITE);
    }
    else 
    {
        m_RunMutex.Unlock();
    }
}

// ---------------------------------------------------------------------------------------------
	
DWORD CThread::ThreadMain(LPVOID _pParams)
{
	CThread::SThreadParameters* pParams = (CThread::SThreadParameters*)_pParams;
    CThread& rThread = *(pParams->m_pObject);
    
    if (pParams->m_Once)
    {
		CB_CRITICAL_SECTION_GUARD(rThread.m_RunMutex)
    	
		while (CriticalSectionTest(rThread.m_PauseMutex, rThread.m_Paused))
			Sleep(pParams->m_Granularity);
        
    	rThread.Tick(pParams->m_pData);
		rThread.m_Running = false;
    }
    else
    {
		rThread.m_RunMutex.Lock();
    	while (rThread.m_Running)
        {
        	if (!CriticalSectionTest(rThread.m_PauseMutex, rThread.m_Paused))
				rThread.Tick(pParams->m_pData);
            
			rThread.m_RunMutex.Unlock();
			Sleep(pParams->m_Granularity);
			rThread.m_RunMutex.Lock();
        }

		rThread.m_Running = false;
		rThread.m_RunMutex.Unlock();
    }
    
    delete pParams;
    return 0;
}

#else

// ---------------------------------------------------------------------------------------------
// POSIX Thread implementation
// ---------------------------------------------------------------------------------------------

CThread::CThread(const char* _pName, uint32 _Granularity)
: m_Granularity(_Granularity)
, m_Paused(false)
, m_pName(_pName)
, m_Running(false)
{
}

// ---------------------------------------------------------------------------------------------

CThread::~CThread()
{
	Stop();
}

// ---------------------------------------------------------------------------------------------
	
void CThread::Run(void* _pData)
{
	CB_CRITICAL_SECTION_GUARD(m_RunMutex);
	
	if (Debug::Verify(!m_Running, "Thread already running"))
    {
		SThreadParameters* pParams = new SThreadParameters();
		
        pParams->m_Granularity = m_Granularity;
        pParams->m_pObject     = this;
        pParams->m_pData       = _pData;
        pParams->m_Once        = false;
        
		pthread_create(&m_ThreadId, NULL, &CThread::ThreadMain, (void*)pParams);
		m_Running = true;
    }
}

// ---------------------------------------------------------------------------------------------

void CThread::RunOnce(void* _pData)
{
    CB_CRITICAL_SECTION_GUARD(m_RunMutex);
	
	if (Debug::Verify(!m_Running, "Thread already running"))
    {
    	SThreadParameters* pParams = new SThreadParameters();
    
        pParams->m_Granularity = 0;
        pParams->m_pObject     = this;
        pParams->m_pData       = _pData;
        pParams->m_Once        = true;
        
        pthread_create(&m_ThreadId, NULL, &CThread::ThreadMain, (void*)pParams);
		m_Running = true;
	}
}

// ---------------------------------------------------------------------------------------------

void CThread::Join()
{
	if (CriticalSectionTest(m_RunMutex, m_Running))
    	pthread_join(m_ThreadId, NULL);
}

// ---------------------------------------------------------------------------------------------

void CThread::Pause()
{
	CB_CRITICAL_SECTION_GUARD(m_PauseMutex);
	m_Paused = true;
}

// ---------------------------------------------------------------------------------------------

void CThread::Resume()
{
	CB_CRITICAL_SECTION_GUARD(m_PauseMutex);
    m_Paused = false;
}

// ---------------------------------------------------------------------------------------------

bool CThread::IsPaused()
{
    return CriticalSectionTest(m_PauseMutex, m_Paused);
}
	
// ---------------------------------------------------------------------------------------------

void CThread::Stop()
{
	m_RunMutex.Lock();
	
	if (m_Running)
	{
		m_Running = false;
		Resume();
		
		m_RunMutex.Unlock();
		pthread_join(m_ThreadId, NULL);
    }
    else 
    {
        m_RunMutex.Unlock();
    }
}

// ---------------------------------------------------------------------------------------------
	
void* CThread::ThreadMain(void* _pParams)
{
	CThread::SThreadParameters* pParams = (CThread::SThreadParameters*)_pParams;
    CThread& rThread = *(pParams->m_pObject);
    
    if (pParams->m_Once)
    {
		CB_CRITICAL_SECTION_GUARD(rThread.m_RunMutex)
    	
		while (CriticalSectionTest(rThread.m_PauseMutex, rThread.m_Paused))
			Sleep(pParams->m_Granularity);
        
    	rThread.Tick(pParams->m_pData);
		rThread.m_Running = false;
    }
    else
    {
		rThread.m_RunMutex.Lock();
    	while (rThread.m_Running)
        {
        	if (!CriticalSectionTest(rThread.m_PauseMutex, rThread.m_Paused))
				rThread.Tick(pParams->m_pData);
            
			rThread.m_RunMutex.Unlock();
			Sleep(pParams->m_Granularity);
			rThread.m_RunMutex.Lock();
        }
		
		rThread.m_Running = false;
		rThread.m_RunMutex.Unlock();
    }
    
    delete pParams;
    return 0;
}

#endif

CB_SUBNAMESPACE_END